ECB Oracle
ECB is the most simple mode, with each plaintext block encrypted entirely independently. In this case, your input is prepended to the secret flag and encrypted and that's it. We don't even provide a decrypt function. Perhaps you don't need a padding oracle when you have an "ECB oracle"?
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
KEY = ?
FLAG = ?
@chal.route('/ecb_oracle/encrypt/<plaintext>/')
def encrypt(plaintext):
plaintext = bytes.fromhex(plaintext)
padded = pad(plaintext + FLAG.encode(), 16)
cipher = AES.new(KEY, AES.MODE_ECB)
try:
encrypted = cipher.encrypt(padded)
except ValueError as e:
return {"error": str(e)}
return {"ciphertext": encrypted.hex()}
Solution:
First we can find flag length by (Brute-Force) sending a data of length l [0-32] and when the length of ciphertext changes means we have found the length of flag.
import requests
url = "http://aes.cryptohack.org/ecb_oracle/encrypt/"
def encrypt(data):
r = requests.get(f"{url}/{data.hex()}/")
return (bytes.fromhex(r.json()["ciphertext"]))
def ascii_to_hex(s):
return ''.join([hex(ord(c))[2:] for c in s])
def flag_len():
cipherlen = 0
targets = [encrypt(b"A"*(i+1)) for i in range(0x10)]
for i in range(16):
ciphertext=targets[i]
if(cipherlen==0):
cipherlen=len(ciphertext)
else:
if(cipherlen!=len(ciphertext)):
return cipherlen - i-1
print(flag_len())
After running the above code we will get the length of flag as 25
Now consider we are sending string of length 31 as "XXXXX..XXX" and ciphertext will be :
- encryption(XXXXX..XXX+"c" +"rypto{....}+padding")
and if we send string of length 32 as "XXXXX..XXX"+"c" and ciphertext will be :
- encryption(XXXXX..XXX+"c" +"crypto{....}+padding")
since it is ECB mode so the ciphertext for first block(64) bytes will be same for both the cases.
Now we know first byte of flag so now we will take 30 length string and check for second byte and so on till we get the '}'.
import requests
import json
url = "http://aes.cryptohack.org/ecb_oracle/encrypt/"
alphabets = "etoanihsrdlucgwyfmpbkvjxqz{}_0123456789ETOANIHSRDLUCGWYFMPBKVJXQZ"
def encrypt(data):
r = requests.get(f"{url}/{data.encode().hex()}/")
return(bytes.fromhex(r.json()["ciphertext"]))
def find_flag():
flag = b"crypto{"
block = b"X"*24 ##since we know flag format and 7+24=31
for i in range(25):
c1 = encrypt(block)
for j in alphabets:
c2=encrypt(block+flag+j)
if(c1[:32]==c2[:32]):
block = block[:-1]
flag+=j
print(flag)
break
if flag[-1] == "}":
break
return flag
print(find_flag())
Another approach for reducing the number of requests is
def encrypt_all(data):
MAX_SIZE = 0x10*56
for i in range((len(data)-1)//MAX_SIZE+1):
block = data[i*MAX_SIZE:(i+1)*MAX_SIZE]
ct = encrypt(block)[:len(block)]
for j in range(len(ct)//0x10):
yield ct[j*0x10:(j+1)*0x10]
def find_flag():
flag = "crypto{" #since we know first 7 charcters of flag
targets=[encrypt("A"*(16-i)) for i in range(16)]
for i in range(7,25):
x,y = (i+1)//16,(i+1)%16
c1 = targets[y][x*16:(x+1)*16]
attempts = ""
for c in alphabets:
attempts+=("A"*16+flag+c)[-16:] #to reduce number of requests on server
for c,c2 in zip(alphabets,encrypt_all(attempts)):
if c1==c2:
flag+=c
print(flag)
break
if flag[-1] == "}":
break
return flag
This will solve problem faster because only one request is sent for each
After running code we will get the flag crypto{p3n6u1n5_h473_3cb}